package com.hospital.dictionary.query;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hospital.common.core.utils.CommonUtils;
import com.hospital.common.core.utils.SqlInjectionUtil;
import com.hospital.common.core.utils.StringUtil;
import com.hospital.dictionary.constant.CommonConstant;
import com.hospital.dictionary.constant.DataBaseConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;

import java.beans.PropertyDescriptor;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class QueryGenerator {

    public static final String SQL_RULES_COLUMN = "SQL_RULES_COLUMN";

    private static final String BEGIN = "_begin";
    private static final String END = "_end";
    /**
     * 数字类型字段，拼接此后缀 接受多值参数
     */
    private static final String MULTI = "_MultiString";
    private static final String STAR = "*";
    private static final String COMMA = ",";
    private static final String NOT_EQUAL = "!";
    /**页面带有规则值查询，空格作为分隔符*/
    private static final String QUERY_SEPARATE_KEYWORD = " ";
    /**高级查询前端传来的参数名*/
    private static final String SUPER_QUERY_PARAMS = "superQueryParams";
    /** 高级查询前端传来的拼接方式参数名 */
    private static final String SUPER_QUERY_MATCH_TYPE = "superQueryMatchType";
    /** 单引号 */
    public static final String SQL_SQ = "'";
    /**排序列*/
    private static final String ORDER_COLUMN = "column";
    /**排序方式*/
    private static final String ORDER_TYPE = "order";
    private static final String ORDER_TYPE_ASC = "ASC";

    /**mysql 模糊查询之特殊字符下划线 （_、\）*/
    public static final String LIKE_MYSQL_SPECIAL_STRS = "_,%";

    private static boolean canChange=false;

    public static <T> QueryWrapper<T> initQueryWrapper(T searchObj, Map<String,String[]> parameterMap){
        long start=System.currentTimeMillis();
        QueryWrapper<T> queryWrapper=new QueryWrapper<>();
        installMplus(queryWrapper,searchObj,parameterMap);
        return queryWrapper;
    }

    public static void installMplus(QueryWrapper<?> queryWrapper, Object searchObj, Map<String,String[]> parameterMap){
        PropertyDescriptor originDescriptors[]= PropertyUtils.getPropertyDescriptors(searchObj);
        String name,type,column;
        Map<String,String> fieldColumnMap = new HashMap<>();
        for(int i=0;i<originDescriptors.length;i++){
            name=originDescriptors[i].getName();
            type=originDescriptors[i].getPropertyType().toString();
            try {
                if(judgedIsUselessField(name)||!PropertyUtils.isReadable(searchObj,name)){
                    continue;
                }
                Object value=PropertyUtils.getSimpleProperty(searchObj,name);
                column=getTableFieldName(searchObj.getClass(),name);
                if(column==null){
                    continue;
                }
                fieldColumnMap.put(name,column);
                doIntervalQuery(queryWrapper,parameterMap,type,name,column);
                if(null!=value&&value.toString().startsWith(COMMA)&&value.toString().endsWith(COMMA)){
                    String multiLikeVal=value.toString().replace(",,",COMMA);
                    String[] vals=multiLikeVal.substring(1,multiLikeVal.length()).split(COMMA);
                    final String field= StringUtil.camelToUnderline(column);
                    if(vals.length>1){
                        queryWrapper.and(j->{
                            j=j.like(field,vals[0]);
                            for (int k=1;k<vals.length;k++){
                                j=j.or().like(field,vals[k]);
                            }
                        });
                    }else {
                        queryWrapper.and(j->j.like(field,vals[0]));
                    }
                }else{
                    QueryRuleEnum rule=convert2Rule(value);
                    value=replaceValue(rule,value);
                    addEasyQuery(queryWrapper,column,rule,value);
                }
            }catch (Exception e){
                log.error(e.getMessage(),e);
            }
        }
        doMuiltiFieldsOrder(queryWrapper,parameterMap);
        doSuperQuery(queryWrapper,parameterMap,fieldColumnMap);
    }

    public static void doSuperQuery(QueryWrapper<?> queryWrapper, Map<String,String[]> parameterMap, Map<String,String> fieldColumnMap){
        if(parameterMap!=null&&parameterMap.containsKey(SUPER_QUERY_PARAMS)){
            String superQueryParams=parameterMap.get(SUPER_QUERY_PARAMS)[0];
            String superQueryMatchType=parameterMap.get(SUPER_QUERY_MATCH_TYPE)!=null?parameterMap.get(SUPER_QUERY_MATCH_TYPE)[0]: MatchTypeEnum.AND.getValue();
            MatchTypeEnum matchType= MatchTypeEnum.getByValue(superQueryMatchType);
            try{
                superQueryParams= URLDecoder.decode(superQueryParams,"UTF-8");
                List<QueryCondition> conditions= JSON.parseArray(superQueryParams, QueryCondition.class);
                if(conditions==null||conditions.size()==0){
                    return;
                }
                queryWrapper.and(andWrapper->{
                    for (int i=0;i<conditions.size();i++){
                        QueryCondition rule=conditions.get(i);
                        if(StringUtil.isNotEmpty(rule.getField())
                            &&StringUtil.isNotEmpty(rule.getRule())
                                &&StringUtil.isNotEmpty(rule.getVal())){
                            addEasyQuery(andWrapper,fieldColumnMap.get(rule.getField()), QueryRuleEnum.getByValue(rule.getRule()),rule.getVal());
                            if(MatchTypeEnum.OR==matchType&&i<(conditions.size()-1)){
                                andWrapper.or();
                            }
                        }
                    }
                });
            }catch (UnsupportedEncodingException ex){

            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public static void doMuiltiFieldsOrder(QueryWrapper<?> queryWrapper, Map<String,String[]> parameterMap){
        String column=null,order=null;
        if(parameterMap!=null&&parameterMap.containsKey(ORDER_COLUMN)){
            column=parameterMap.get(ORDER_COLUMN)[0];
        }
        if(parameterMap!=null&&parameterMap.containsKey(ORDER_TYPE)){
            order=parameterMap.get(ORDER_TYPE)[0];
        }
        if(StringUtil.isNotEmpty(column)&&StringUtil.isNotEmpty(order)){
            if(column.endsWith(CommonConstant.DICT_TEXT_SUFFIX)){
                column=column.substring(0,column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
            }
            SqlInjectionUtil.filterContent(column);
            if(order.toUpperCase().indexOf(ORDER_TYPE_ASC)>0){
                queryWrapper.orderByAsc(StringUtil.camelToUnderline(column));
            }else{
                queryWrapper.orderByDesc(StringUtil.camelToUnderline(column));
            }
        }
    }

    private static void addEasyQuery(QueryWrapper<?> queryWrapper, String name, QueryRuleEnum rule, Object value){
        if(value==null||rule==null||StringUtil.isEmpty(value)){
            return;
        }
        if(canChange) {
            name = StringUtil.camelToUnderline(name);
        }
        switch (rule){
            case GT:
                queryWrapper.gt(name,value);
                break;
            case GE:
                queryWrapper.ge(name,value);
                break;
            case LT:
                queryWrapper.lt(name,value);
                break;
            case LE:
                queryWrapper.le(name,value);
                break;
            case EQ:
                queryWrapper.eq(name,value);
                break;
            case NE:
                queryWrapper.ne(name,value);
                break;
            case IN:
                if(value instanceof String){
                    queryWrapper.in(name, (Object[]) value.toString().split(","));
                }else if(value instanceof String[]){
                    queryWrapper.in(name,(Object[]) value);
                }else if(value.getClass().isArray()){
                    queryWrapper.in(name,(Object[]) value);
                }else {
                    queryWrapper.in(name,value);
                }
                break;
            case LIKE:
                queryWrapper.like(name,value);
                break;
            case LEFT_LIKE:
                queryWrapper.likeLeft(name,value);
                break;
            case RIGHT_LIKE:
                queryWrapper.likeRight(name,value);
                break;
            default:
                break;
        }
    }

    private static QueryRuleEnum convert2Rule(Object value){
        if(value==null){
            return null;
        }
        String val=(value+"").toString().trim();
        if(val.length()==0){
            return null;
        }

        QueryRuleEnum rule=null;

        if(rule==null&&val.length()>=3){
            if(QUERY_SEPARATE_KEYWORD.equals(val.substring(2,3))){
                rule= QueryRuleEnum.getByValue(val.substring(0,2));
            }
        }
        if(rule==null&&val.length()>=2){
            if(QUERY_SEPARATE_KEYWORD.equals(val.substring(1,2))){
                rule= QueryRuleEnum.getByValue(val.substring(0,1));
            }
        }
        if(rule==null&&val.contains(STAR)){
            if(val.startsWith(STAR)&&val.endsWith(STAR)){
                rule= QueryRuleEnum.LIKE;
            }else if(val.startsWith(STAR)){
                rule= QueryRuleEnum.LEFT_LIKE;
            }else if(val.endsWith(STAR)){
                rule= QueryRuleEnum.RIGHT_LIKE;
            }
        }
        if(rule==null&&val.contains(COMMA)){
            rule= QueryRuleEnum.IN;
        }
        if(rule==null&&val.startsWith(NOT_EQUAL)){
            rule= QueryRuleEnum.NE;
        }
        return rule!=null?rule: QueryRuleEnum.EQ;
    }

    private static Object replaceValue(QueryRuleEnum rule, Object value){
        if(rule==null){
            return null;
        }
        if(!(value instanceof String)){
            return value;
        }
        String val=(value+"").toString().trim();
        if(rule== QueryRuleEnum.LIKE){
            value=val.substring(1,val.length()-1);
            value=specialStrConvert(value.toString());
        }else if(rule== QueryRuleEnum.LEFT_LIKE||rule== QueryRuleEnum.NE){
            value=val.substring(1);
            value=specialStrConvert(value.toString());
        }else if(rule== QueryRuleEnum.RIGHT_LIKE){
            value=val.substring(0,val.length()-1);
            value=specialStrConvert(value.toString());
        }else if(rule== QueryRuleEnum.IN){
            value=val.split(",");
        }else {
            if(val.startsWith(rule.getValue())){
                value=val.replaceFirst(rule.getValue(),"");
            }else if(val.startsWith(rule.getCondition()+QUERY_SEPARATE_KEYWORD)){
                value=val.replaceFirst(rule.getCondition()+QUERY_SEPARATE_KEYWORD,"").trim();
            }
        }
        return value;
    }

    private static String getDbType(){
        return CommonUtils.getDatabaseType();
    }

    private static String specialStrConvert(String value){
        if(DataBaseConstant.DB_TYPE_MYSQL.equals(getDbType())){
            String[] special_str= QueryGenerator.LIKE_MYSQL_SPECIAL_STRS.split(",");
            for(String str:special_str){
                if(value.indexOf(str)!=-1){
                    value=value.replace(str,"\\"+str);
                }
            }
        }
        return value;
    }

    private static void addQueryByRule(QueryWrapper<?> queryWrapper, String name, String type, String value, QueryRuleEnum rule) throws ParseException{
        if(StringUtil.isNotEmpty(value)){
            Object temp;
            if(value.indexOf(COMMA)!=-1){
                temp=value;
                addEasyQuery(queryWrapper,name,rule,temp);
                return;
            }

            switch (type){
                case "class java.lang.Integer":
                    temp=Integer.parseInt(value);
                    break;
                case "class java.math.BigDecimal":
                    temp=new BigDecimal(value);
                    break;
                case "class java.lang.Short":
                    temp=Short.parseShort(value);
                    break;
                case "class java.lang.Long":
                    temp=Long.parseLong(value);
                    break;
                case "class java.lang.Float":
                    temp=Float.parseFloat(value);
                    break;
                case "class java.lang.Double":
                    temp=Double.parseDouble(value);
                    break;
                case "class java.util.Date":
                    temp=getDateQueryByRule(value,rule);
                    break;
                default:
                    temp=value;
                    break;
            }
            addEasyQuery(queryWrapper,name,rule,temp);
        }
    }
    private static final ThreadLocal<SimpleDateFormat> local=new ThreadLocal<>();
    private static SimpleDateFormat getTime(){
        SimpleDateFormat time=local.get();
        if(time==null){
            time=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            local.set(time);
        }
        return time;
    }

    private static Date getDateQueryByRule(String value, QueryRuleEnum rule) throws ParseException{
        Date date=null;
        if(value.length()==10){
            if(rule== QueryRuleEnum.GE){
                date=getTime().parse(value+" 00:00:00");
            }else if(rule== QueryRuleEnum.LE){
                date=getTime().parse(value+" 23:59:59");
            }
        }
        if(date==null){
            date=getTime().parse(value);
        }
        return date;
    }

    private static void doIntervalQuery(QueryWrapper<?> queryWrapper, Map<String,String[]> parameterMap, String type, String fieldName, String columnName) throws ParseException{
        String endValue=null,beginValue=null;
        if(parameterMap!=null&&parameterMap.containsKey(fieldName+BEGIN)){
            beginValue=parameterMap.get(fieldName+BEGIN)[0].trim();
            addQueryByRule(queryWrapper,columnName,type,beginValue, QueryRuleEnum.GE);
        }
        if(parameterMap!=null&&parameterMap.containsKey(fieldName+END)){
            endValue=parameterMap.get(fieldName+END)[0].trim();
            addQueryByRule(queryWrapper,columnName,type,endValue, QueryRuleEnum.LE);
        }
        if(parameterMap!=null&&parameterMap.containsKey(fieldName+MULTI)){
            endValue=parameterMap.get(fieldName+MULTI)[0].trim();
            addQueryByRule(queryWrapper,columnName.replace(MULTI,""),type,endValue, QueryRuleEnum.IN);
        }
    }

    private static boolean judgedIsUselessField(String name){
        return "class".equals(name)||"ids".equals(name)
                ||"page".equals(name)||"rows".equals(name)
                ||"sort".equals(name)||"order".equals(name);
    }

    private static List<Field> getClassFields(Class<?> clazz){
        List<Field> list=new ArrayList<>();
        Field[] fields;
        do{
            fields=clazz.getDeclaredFields();
            for (int i=0;i<fields.length;i++){
                list.add(fields[i]);
            }
        }while (clazz!=Object.class&&clazz!=null);
        return list;
    }

    private static String getTableFieldName(Class<?> clazz,String name){
        try{
            canChange=true;
            Field field=null;
            try{
                field=clazz.getDeclaredField(name);
            }catch (NoSuchFieldException e){

            }

            if(field==null){
                List<Field> allFields=getClassFields(clazz);
                List<Field> searchFields=allFields.stream().filter(a->a.getName().equals(name)).collect(Collectors.toList());
                if(searchFields!=null&&searchFields.size()>0){
                    field=searchFields.get(0);
                }
            }

            if(field!=null){
                TableField tableField=field.getAnnotation(TableField.class);
                if(tableField!=null){
                    if(tableField.exist()==false){
                        return null;
                    }else {
                        String column=tableField.value();
                        if(!"".equals(column)){
                            canChange=false;
                            return column;
                        }
                    }
                }
                TableLogic tableLogic=field.getAnnotation(TableLogic.class);
                if(tableLogic!=null){
                    return null;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return name;
    }
}
